// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: jshin

#pragma once

#include <string>
#include <string_view>

#include "coin2/exchange/base/executor/packet.h"
#include "coin2/exchange/base/feed_util/book_builder.h"
#include "coin2/exchange/base/feed_util/bbo_container.h"
#include "coin2/exchange/base/feed_util/liquidation_order.h"
#include "coin2/exchange/base/feed_util/index.h"
#include "coin2/exchange/base/feed_util/mark_price.h"
#include "coin2/exchange/base/feed_util/funding_rate.h"
#include "coin2/exchange/base/feed_util/open_interest.h"
#include "coin2/exchange/base/feed_util/iv_greeks.h"
#include "coin2/exchange/base/feed_util/nav.h"
#include "coin2/exchange/base/feed_util/kline.h"
#include "coin2/exchange/base/feed_util/trade.h"
#include "coin2/exchange/base/feed_util/wallet.h"
#include "coin2/exchange/base/feed_util/market_analysis.h"
#include "coin2/exchange/base/feed_util/trading_data.h"
#include "coin2/exchange/base/market/enums.h"
#include "coin2/exchange/base/market/market_exchange_api.h"
#include "coin2/exchange/base/symbology/product.h"

namespace coin2::exchange::base::feed {
namespace impl {
using coin2::exchange::base::market::ExchangeType;
using coin2::exchange::base::market::MarketExchangeApi;
using coin2::exchange::base::market::MarketType;
using coin2::exchange::base::symbology::IProduct;
using coin2::exchange::base::executor::TopicRecordUpdate;
using coin2::exchange::base::executor::IPacketSubscriber;
using coin2::exchange::base::feed_util::Index;

class FeedUpdate : public TopicRecordUpdate {
 public:
  explicit FeedUpdate(const std::string& system_name, const MarketExchangeApi& mea)
      : system_name_(system_name),
        mea_(mea),
        feed_type_{coin2::exchange::feed::UNKNOWN_FEED},
        native_symbol_{},
        product_{nullptr},
        book_(nullptr),
        trade_(nullptr),
        liquidation_order_(&empty_liquidation_order_),
        open_interest_(&empty_open_interest_),
        mark_price_(&empty_mark_price_),
        index_(&empty_index_),
        funding_rate_(&empty_funding_rate_),
        nav_(&empty_nav_),
        klines_(&empty_klines_),
        wallet_(&empty_wallet_),
        market_analysis_(&empty_market_analysis_),
        top_long_short_account_ratio_(&empty_top_long_short_account_ratio_),
        top_long_short_position_ratio_(&empty_top_long_short_position_ratio_),
        global_long_short_account_ratio_(&empty_global_long_short_account_ratio_),
        taker_buy_sell_ratio_(&empty_taker_buy_sell_ratio_),
        bbo_est_(nullptr),
        empty_book_(false) {}
  ~FeedUpdate() override { Reset(); }

  const std::string& system_name() const { return system_name_; }
  MarketType market() const { return mea_.market; }
  ExchangeType exchange() const { return mea_.exchange; }
  const MarketExchangeApi& mea() const { return mea_; }

  coin2::exchange::feed::FeedType feed_type() const { return feed_type_; }
  bool is_book() const { return feed_type_ == coin2::exchange::feed::BOOK_FEED; }
  bool is_trade() const { return feed_type_ == coin2::exchange::feed::TRADE_FEED; }
  bool is_liquidation() const {
    return (
        feed_type_ == coin2::exchange::feed::STATUS_FEED
        && liquidation_order_ != nullptr
        && liquidation_order_ != &empty_liquidation_order_);
  }
  bool is_index() const {
    return (
        feed_type_ == coin2::exchange::feed::STATUS_FEED
        && index_ != nullptr
        && index_ != &empty_index_);
  }
  bool is_funding_rate() const {
    return (
        feed_type_ == coin2::exchange::feed::STATUS_FEED
        && funding_rate_ != nullptr
        && funding_rate_ != &empty_funding_rate_);
  }
  bool is_open_interest() const {
    return (
        feed_type_ == coin2::exchange::feed::STATUS_FEED
        && open_interest_ != nullptr
        && open_interest_ != &empty_open_interest_);
  }
  bool is_wallet() const {
    return (
      feed_type_ == coin2::exchange::feed::STATUS_FEED
      && wallet_ != nullptr
      && wallet_ != &empty_wallet_);
  }
  bool is_market_analysis() const {
    return (
      feed_type_ == coin2::exchange::feed::STATUS_FEED
      && market_analysis_ != nullptr
      && market_analysis_ != &empty_market_analysis_);
  }
  bool is_mark_price() const {
    return (
      feed_type_ == coin2::exchange::feed::STATUS_FEED
      && mark_price_ != nullptr
      && mark_price_ != &empty_mark_price_);
  }
  bool is_top_long_short_account_ratio() const {
    return (
      feed_type_ == coin2::exchange::feed::STATUS_FEED
      && top_long_short_account_ratio_ != nullptr
      && top_long_short_account_ratio_ != &empty_top_long_short_account_ratio_);
  }
  bool is_top_long_short_position_ratio() const {
    return (
      feed_type_ == coin2::exchange::feed::STATUS_FEED
      && top_long_short_position_ratio_ != nullptr
      && top_long_short_position_ratio_ != &empty_top_long_short_position_ratio_);
  }
  bool is_global_long_short_account_ratio() const {
    return (
      feed_type_ == coin2::exchange::feed::STATUS_FEED
      && global_long_short_account_ratio_ != nullptr
      && global_long_short_account_ratio_ != &empty_global_long_short_account_ratio_);
  }

  std::string_view native_symbol() const { return native_symbol_; }
  const coin2::exchange::base::symbology::IProduct& product() const { return *product_; }

  const IBook& book() const {
    if (feed_type_ == coin2::exchange::feed::BOOK_FEED) {
      return static_cast<const IBook&>(*book_);
    } else {
      return empty_book_;
    }
  }
  const Trade& trade() const {
    if (trade_ != nullptr) {
      return *trade_;
    } else {
      return empty_trade_;
    }
  }
  const LiquidationOrder& liquidation_order() const {
    if (liquidation_order_ != nullptr) {
      return *liquidation_order_;
    } else {
      return empty_liquidation_order_;
    }
  }
  const OpenInterest& open_interest() const {
    if (open_interest_ != nullptr) {
      return *open_interest_;
    } else {
      return empty_open_interest_;
    }
  }
  const MarkPrice& mark_price() const {
    if (mark_price_ != nullptr) {
      return *mark_price_;
    } else {
      return empty_mark_price_;
    }
  }
  const Index& index() const {
    if (index_ != nullptr) {
      return *index_;
    } else {
      return empty_index_;
    }
  }
  const FundingRate& funding_rate() const {
    if (funding_rate_ != nullptr) {
      return *funding_rate_;
    } else {
      return empty_funding_rate_;
    }
  }
  const IvGreeks& iv_greeks() const {
    if (iv_greeks_ != nullptr) {
      return *iv_greeks_;
    } else {
      return empty_iv_greeks_;
    }
  }
  const Nav& nav() const {
    if (nav_ != nullptr) {
      return *nav_;
    } else {
      return empty_nav_;
    }
  }
  const Klines& klines() const {
    if (klines_ != nullptr) {
      return *klines_;
    } else {
      return empty_klines_;
    }
  }

  const Wallet& wallet() const{
    if (wallet_ != nullptr) {
      return *wallet_;
    } else {
      return empty_wallet_;
    }
  }

  const MarketAnalysis& market_analysis() const{
    if (market_analysis_ != nullptr) {
      return *market_analysis_;
    } else {
      return empty_market_analysis_;
    }
  }

  const TradingData<TopLongShortAccountRatio>& top_long_short_account_ratio() const{
    if (top_long_short_account_ratio_ != nullptr) {
      return *top_long_short_account_ratio_;
    } else {
      return empty_top_long_short_account_ratio_;
    }
  }

  const TradingData<TopLongShortPositionRatio>& top_long_short_position_ratio() const{
    if (top_long_short_position_ratio_ != nullptr) {
      return *top_long_short_position_ratio_;
    } else {
      return empty_top_long_short_position_ratio_;
    }
  }

  const TradingData<GlobalLongShortAccountRatio>& global_long_short_account_ratio() const{
    if (global_long_short_account_ratio_ != nullptr) {
      return *global_long_short_account_ratio_;
    } else {
      return empty_global_long_short_account_ratio_;
    }
  }

  const TradingData<TakerBuySellRatio>& taker_buy_sell_ratio() const{
    if (taker_buy_sell_ratio_ != nullptr) {
      return *taker_buy_sell_ratio_;
    } else {
      return empty_taker_buy_sell_ratio_;
    }
  }
  const TradingData<OpenInterest>& open_interest_hist() const{
    if (open_interest_hist_ != nullptr) {
      return *open_interest_hist_;
    } else {
      return empty_open_interest_hist_;
    }
  }

  void SetBboEst(const BboContainer* bbo_est) const {
    bbo_est_ = bbo_est;
  }

  const BboContainer* GetBboEst() const {
    return bbo_est_;
  }

 public:
  void Reset() override {
    ResetTopicRecordUpdate();
    ResetFeedUpdate();
  }

  void ResetFeedUpdate() {
    feed_type_ = coin2::exchange::feed::UNKNOWN_FEED;
    native_symbol_ = {};
    product_ = nullptr;
    book_ = nullptr;
    trade_ = nullptr;
    liquidation_order_ = nullptr;
    open_interest_ = nullptr;
    mark_price_ = nullptr;
    index_ = nullptr;
    funding_rate_ = nullptr;
    nav_ = nullptr;
    klines_ = nullptr;
    wallet_ = nullptr;
    market_analysis_ = nullptr;
    top_long_short_account_ratio_ = nullptr;
    top_long_short_position_ratio_ = nullptr;
    global_long_short_account_ratio_ = nullptr;
    taker_buy_sell_ratio_ = nullptr;
    open_interest_hist_ = nullptr;
  }

  void SetType(std::string_view native_symbol, const IProduct& product,
               coin2::exchange::feed::FeedType feed_type) {
    product_ = &product;
    feed_type_ = feed_type;
    native_symbol_ = native_symbol;
  }

  void SetType(coin2::exchange::feed::FeedType feed_type) {
    feed_type_ = feed_type;
  }

  void SetRecipe(const std::string& recipe) { recipe_ = recipe; }
  const std::string& recipe() const { return recipe_; }
  void SetFeedType(coin2::exchange::feed::FeedType feed_type) { feed_type_ = feed_type; }
  void SetBook(const BookBuilder* book) { book_ = book; }
  void SetTrade(const Trade* trade) { trade_ = trade; }
  void SetLiquidationOrder(const LiquidationOrder* liquidation_order) {
    liquidation_order_ = liquidation_order;
  }
  void SetOpenInterest(const OpenInterest* open_interest) {
    open_interest_ = open_interest;
  }
  void SetMarkPrice(const MarkPrice* mark_price) {
    mark_price_ = mark_price;
  }
  void SetIndex(const Index* index) {
    index_ = index;
  }
  void SetFundingRate(const FundingRate* funding_rate) {
    funding_rate_ = funding_rate;
  }
  void SetIvGreeks(const IvGreeks* iv_greeks) {
    iv_greeks_ = iv_greeks;
  }
  void SetNav(const Nav* nav) {
    nav_ = nav;
  }
  void SetKlines(const Klines* klines) {
    klines_ = klines;
  }
  void SetWallet(const Wallet* wallet) {
    wallet_ = wallet;
  }
  void SetMarketAnalysis(const MarketAnalysis* market_analysis) {
    market_analysis_ = market_analysis;
  }
  void SetTopLongShortAccountRatio(const TradingData<TopLongShortAccountRatio>* ratio) {
    top_long_short_account_ratio_ = ratio;
  }
  void SetTopLongShortPositionRatio(const TradingData<TopLongShortPositionRatio>* ratio) {
    top_long_short_position_ratio_ = ratio;
  }
  void SetGlobalLongShortAccountRatio(const TradingData<GlobalLongShortAccountRatio>* ratio) {
    global_long_short_account_ratio_ = ratio;
  }
  void SetTakerBuySellRatio(const TradingData<TakerBuySellRatio>* ratio) {
    taker_buy_sell_ratio_ = ratio;
  }
  void SetOpenInterestHist(const TradingData<OpenInterest>* open_interest_hist) {
    open_interest_hist_ = open_interest_hist;
  }
  void SetNativeSymbol(std::string_view symbol) { native_symbol_ = symbol; }
  void SetProduct(const IProduct& product) { product_ = &product; }

  const BookBuilder* GetBookBuilder() const { return book_; }

 protected:
  // must be const
  const std::string system_name_;
  const MarketExchangeApi mea_;
  std::string recipe_;

  coin2::exchange::feed::FeedType feed_type_;

  std::string_view native_symbol_;
  const IProduct* product_;

  // references are not re-bindable
  const BookBuilder* book_;
  const Trade* trade_;
  const LiquidationOrder* liquidation_order_;
  const OpenInterest* open_interest_;
  const MarkPrice* mark_price_;
  const Index* index_;
  const FundingRate* funding_rate_;
  const IvGreeks* iv_greeks_;
  const Nav* nav_;
  const Klines* klines_;
  const Wallet* wallet_;
  const MarketAnalysis* market_analysis_;
  const TradingData<TopLongShortAccountRatio>* top_long_short_account_ratio_;
  const TradingData<TopLongShortPositionRatio>* top_long_short_position_ratio_;
  const TradingData<GlobalLongShortAccountRatio>* global_long_short_account_ratio_;
  const TradingData<TakerBuySellRatio>* taker_buy_sell_ratio_;
  const TradingData<OpenInterest>* open_interest_hist_;
  mutable const BboContainer* bbo_est_;
  BookBuilder empty_book_;
  Trade empty_trade_{};
  const LiquidationOrder empty_liquidation_order_{};
  const OpenInterest empty_open_interest_{};
  const MarkPrice empty_mark_price_{};
  const Index empty_index_{};
  const FundingRate empty_funding_rate_{};
  const IvGreeks empty_iv_greeks_{};
  const Nav empty_nav_{};
  const Klines empty_klines_{};
  const Wallet empty_wallet_{};
  const MarketAnalysis empty_market_analysis_{};
  const TradingData<TopLongShortAccountRatio> empty_top_long_short_account_ratio_{};
  const TradingData<TopLongShortPositionRatio> empty_top_long_short_position_ratio_{};
  const TradingData<GlobalLongShortAccountRatio> empty_global_long_short_account_ratio_{};
  const TradingData<TakerBuySellRatio> empty_taker_buy_sell_ratio_{};
  const TradingData<OpenInterest> empty_open_interest_hist_{};

 private:
  FeedUpdate(const FeedUpdate&) = delete;
  FeedUpdate& operator=(const FeedUpdate&) = delete;
};

}  // namespace impl

using impl::FeedUpdate;

}  // namespace coin2::exchange::base::feed
